import json

from tortoise import Tortoise
from tortoise.exceptions import OperationalError
from tortoise.transactions import in_transaction
from tortoise.query_utils import Q

from app.lib.txt import path_str_arr
from app.model import mysql
from app.service.search import index_my_media


async def new_folder(user_id, name, description='', parent_folder_id=0, folder_types=2, filter_condition=''):
    """
    新增文件夹
    :param user_id:
    :param name:
    :param description:
    :param parent_folder_id:
    :param folder_types:
    :param filter_condition:
    :return:
    """
    parent_path = ''
    level = 0
    if parent_folder_id:
        parent_folder = await mysql.Folder.get_or_none(id=parent_folder_id)
        if parent_folder:
            parent_path = parent_folder.path or ''
            level = parent_path.count('/')
    folder = await mysql.Folder.create(
        user_id=user_id,
        name=name,
        description=description,
        parent_folder_id=parent_folder_id,
        folder_types=folder_types,
        filter_condition=filter_condition,
        parent_path=parent_path,
        level=level,
    )
    folder.path = path_str_arr(parent_path, folder.id)
    await folder.save()
    return folder


async def get_folders(user_id):
    """
    获取用户的所有文件夹列表
    :param user_id:
    :return:
    """
    conn = Tortoise.get_connection("default")
    items = await conn.execute_query_dict(f'SELECT id, name, level, path, parent_path FROM folder WHERE user_id={user_id} ORDER BY created_time DESC')
    level_set = set([])
    level_dict = {}
    for item in items:
        nid = item.get('id', 0)
        level = item.get('level', 0)
        level_set.add(level)
        # 获取层级的路径列表
        level_ids = level_dict.get(level, [])
        level_ids.append(nid)
        level_dict[level] = level_ids
    levels = list(level_set)
    levels.sort()
    if len(levels) == 0:
        return []

    nodes = list()

    class Node(object):
        def __init__(self, nid: int, name: str, path: str, parent_path: str, children: list):
            """
            初始化
            :param name:
            :param desc:
            :param parent:
            :param children:
            """
            self.id = nid
            self.name = name
            self.path = path
            self.parent_path = parent_path
            self.children = children

        def get_nodes(self):
            """
            获取该节点下的全部结构字典
            """
            d = dict()
            d['id'] = self.id
            d['name'] = self.name
            # d['path'] = self.path
            # d['parent_path'] = self.parent_path
            children = self.get_children()
            if children:
                d['list'] = [child.get_nodes() for child in children]
            return d

        def get_children(self):
            """
            获取该节点下的全部节点对象
            """
            return [n for n in nodes if n.parent_path == self.path]

        def __repr__(self):
            return self.path

    # 将原始数据转换为节点对象
    for data in items:
        node = Node(data.get('id'), data.get('name', ''), data.get('path', ''), data.get('parent_path', ''), [])
        nodes.append(node)

    # 为各个节点建立联系
    for node in nodes:
        children_ids = [data.get('id') for data in items if data.get('parent_path') == node.path]
        children = [node for node in nodes if node.id in children_ids]
        node.children.extend(children)

    # 获取根阶段
    root_node_ids = level_dict.get(levels[0], [])
    root_nodes = [node for node in nodes if node.id in root_node_ids]
    nest_items = []
    for root_node in root_nodes:
        tree = root_node.get_nodes()
        nest_items.append(tree)

    return nest_items


async def create_folder_media_photo(user_id, exif: dict, folder_id):
    """
    创建图片媒体
    :param user_id:
    :param exif: 七牛返回的数据，格式为：
        {
            "key": "",
            "size": 1,
            "type": "",
            "hash": "",
            "width": 0,
            "height": 0,
            "orientation": "",
            "color": "",
            "videoDuration": "18.3434",
            "videoWidth": 0,
            "videoHeight": 0,
            "videoRotate": "",
            "url": "",
            "originalUrl": ""
        }
    :param folder_id:
    :return:
    """
    folder = await mysql.Folder.get_or_none(id=folder_id)
    assert folder

    file_exif = exif.copy()
    if 'url' in file_exif:
        del file_exif['url']
    if 'originalUrl' in file_exif:
        del file_exif['originalUrl']
    # 图片表新增
    photo = await mysql.Photo.create(
        key=exif.get('key', ''),
        width=file_exif.get('width', 0),
        height=file_exif.get('height', 0),
        file_size=file_exif.get('size', 0),
        file_exif=json.dumps(file_exif, ensure_ascii=False),
    )
    # 媒体表新增
    media = await mysql.Media.create(
        object_type=mysql.Media.Type.photo,
        object_id=photo.id,
        user_id=user_id,
    )
    # 我的图片新增
    await mysql.MyMedia.create(
        user_id=user_id,
        media_id=media.id,
        created_time=media.created_time,
    )
    # 文件夹文件新增
    await mysql.FolderFile.create(
        folder_id=folder_id,
        media_id=media.id,
        created_time=media.created_time,
        user_id=user_id,
    )
    # 更新文件夹媒体个数
    await incr_media_number(folder_id, incr=1)
    # 更新文件夹封面
    await reset_folder_and_parent_covers(folder_id)
    # 索引(不存在则删除)
    await index_my_media(user_id, media.id)


async def incr_media_number(folder_id, incr=1, folder=None, conn=None):
    """
    文件夹媒体文件数自增
    :param folder_id:
    :param incr:
    :param folder: 文件夹对象，不存在默认用folder_id对应的对象
    :param conn: 数据库对象
    :return:
    """
    if not incr:
        return
    folder = await mysql.Folder.get_or_none(id=folder_id) if not folder else folder
    if not folder:
        return
    parent_path = folder.parent_path or ''
    parent_ids = parent_path.strip('/').replace('/', ',') if parent_path else ''
    conn = Tortoise.get_connection("default") if not conn else conn
    if parent_ids:
        await conn.execute_query(
            f'UPDATE folder SET sum_media_number = sum_media_number + {incr} WHERE id in ({parent_ids})')
    await conn.execute_query(f'UPDATE folder SET media_number = media_number + {incr}, sum_media_number = sum_media_number + {incr} WHERE id = {folder_id}')


async def get_folder(folder_id: int):
    """
    获取文件夹
    :param folder_id:
    :return:
    """
    return await mysql.Folder.get_or_none(id=folder_id)


async def get_folder_covers(folder_id: int):
    """
    获取文件夹封面
    :param folder_id:
    :return:
    """
    return await mysql.FolderCovers.get_or_none(id=folder_id)


async def remove_all_folder_media(user_id: int, media_id: int):
    """
    从所有文件夹删除媒体
    :param user_id:
    :param media_id:
    :return:
    """
    file_count = await mysql.FolderFile.filter(user_id=user_id, media_id=media_id).count()
    if file_count == 0:
        return
    # 获取媒体所有所在的文件夹编号列表
    folder_ids = await mysql.FolderFile.filter(user_id=user_id, media_id=media_id).values_list('folder_id', flat=True)
    folder_ids = list(set(folder_ids))
    for folder_id in folder_ids:
        await remove_folder_media(user_id, folder_id, media_id)
        # 更新文件夹封面
        await reset_folder_and_parent_covers(folder_id)
    # 索引(不存在则删除)
    await index_my_media(user_id, media_id)


async def remove_folder_media(user_id: int, folder_id: int, media_id: int):
    """
    从文件夹删除媒体
    :param user_id:
    :param folder_id:
    :param media_id:
    :return:
    """
    file_count = await mysql.FolderFile.filter(user_id=user_id, media_id=media_id).count()
    if file_count == 0:
        return
    folder = await mysql.Folder.get_or_none(id=folder_id)
    if not folder:
        return
    folder_file = await mysql.FolderFile.get_or_none(folder_id=folder_id, media_id=media_id)
    media = None
    if file_count == 1:
        media = await mysql.Media.get_or_none(id=media_id)
    try:
        async with in_transaction("default") as tconn:
            defaults = {
                'folder_id': folder_file.folder_id,
                'media_id': folder_file.media_id,
                'created_time': folder_file.created_time,
                'user_id': folder_file.user_id,
            }
            # 文件夹删除图片
            await folder_file.delete(using_db=tconn)
            # 移动到回收站
            await mysql.RecycleBin.get_or_create(id=folder_file.id, defaults=defaults, using_db=tconn)
            # 文件夹是收藏夹，则删除收藏关系
            if folder.folder_types == mysql.Folder.Type.collect:
                await mysql.Collect.filter(user_id=user_id, media_id=media_id).using_db(tconn).delete()
            if file_count == 1:
                # 从我的图片中删除
                await mysql.MyMedia.filter(user_id=user_id, media_id=media_id).using_db(tconn).delete()
                # 是否是自己上传的图片
                if media and media.user_id == user_id:
                    await mysql.Media.filter(id=media_id).using_db(tconn).update(is_delete=True)
            # 更新文件夹媒体个数
            await incr_media_number(folder_id, incr=-1, folder=folder, conn=tconn)
    except OperationalError:
        pass


async def remove_folder_medias(user_id: int, folder_id: int, media_ids: list):
    """
    从文件夹删除一组媒体
    :param user_id:
    :param folder_id:
    :param media_ids:
    :return:
    """
    if not folder_id:
        # 从所有文件夹中删除
        for media_id in media_ids:
            await remove_all_folder_media(user_id, media_id)
    else:
        # 从指定文件夹中删除
        for media_id in media_ids:
            await remove_folder_media(user_id, folder_id, media_id)
            # 索引(不存在则删除)
            await index_my_media(user_id, media_id)
        # 更新文件夹封面
        await reset_folder_and_parent_covers(folder_id)


async def remove_folder_all_medias(user_id: int, folder_id: int):
    """
    清空文件夹的所有媒体
    :param user_id:
    :param folder_id:
    :return:
    """
    media_ids = await mysql.FolderFile.filter(folder_id=folder_id).values_list('media_id', flat=True)
    await remove_folder_medias(user_id, folder_id, media_ids)


async def copy_media2folder(user_id: int, folder_id: int, media_id: int):
    """
    复制媒体到指定文件夹
    :param user_id:
    :param folder_id:
    :param media_id:
    :return:
    """
    count = await mysql.FolderFile.filter(folder_id=folder_id, media_id=media_id).count()
    if count == 0:
        async with in_transaction("default") as tconn:
            # 文件夹文件新增
            await mysql.FolderFile.create(
                folder_id=folder_id,
                media_id=media_id,
                user_id=user_id,
                using_db=tconn,
            )
            # 更新文件夹媒体个数
            await incr_media_number(folder_id, incr=1, conn=tconn)
        # 索引(不存在则删除)
        await index_my_media(user_id, media_id)


async def copy_medias2folder(user_id: int, folder_id: int, media_ids: list):
    """
    复制一组媒体到指定文件夹
    :param user_id:
    :param folder_id:
    :param media_ids:
    :return:
    """
    for media_id in media_ids:
        await copy_media2folder(user_id, folder_id, media_id)
    # 更新文件夹封面
    await reset_folder_and_parent_covers(folder_id)


async def move_media2folder(user_id: int, from_folder_id: int, to_folder_id: int, media_id: int):
    """
    移动一个媒体到指定文件夹
    :param user_id:
    :param from_folder_id:
    :param to_folder_id:
    :param media_id:
    :return:
    """
    from_folder_file = await mysql.FolderFile.get_or_none(folder_id=from_folder_id, media_id=media_id)
    if not from_folder_file:
        return
    to_folder_file = await mysql.FolderFile.get_or_none(folder_id=to_folder_id, media_id=media_id)
    async with in_transaction("default") as tconn:
        if not to_folder_file:
            from_folder_file.folder_id = to_folder_id
            await from_folder_file.save(using_db=tconn)
            await incr_media_number(from_folder_id, incr=-1, conn=tconn)
            await incr_media_number(to_folder_id, incr=1, conn=tconn)
        else:
            await from_folder_file.delete(using_db=tconn)
            await incr_media_number(from_folder_id, incr=-1, conn=tconn)
    # 索引(不存在则删除)
    await index_my_media(user_id, media_id)


async def move_medias2folder(user_id: int, from_folder_id: int, to_folder_id: int, media_ids: list):
    """
    移动一组媒体到指定文件夹
    :param user_id:
    :param from_folder_id:
    :param to_folder_id:
    :param media_ids:
    :return:
    """
    for media_id in media_ids:
        await move_media2folder(user_id, from_folder_id, to_folder_id, media_id)
    # 更新文件夹封面
    await reset_folder_and_parent_covers(from_folder_id)
    await reset_folder_and_parent_covers(to_folder_id)


async def delete_folder(user_id: int, folder_id: int):
    """
    删除某一个文件夹（包含子文件夹）
    :param user_id:
    :param folder_id:
    :return:
    """
    # 获取子文件夹编号
    conn = Tortoise.get_connection("default")
    items = await conn.execute_query_dict(
        "SELECT e2.id FROM folder e1, folder e2 "
        f"WHERE e1.user_id={user_id} AND e1.id={folder_id} AND e2.path like concat(e1.path,'/%')")
    child_folder_ids = [i.get('id') for i in items]
    folder_ids = [folder_id]
    folder_ids.extend(child_folder_ids)
    # 删除文件文件，含子文件夹
    for f_id in folder_ids:
        await remove_folder_all_medias(user_id, f_id)
    # 更新文件夹封面
    await reset_folder_and_parent_covers(folder_id)
    # 删除文件夹
    await mysql.Folder.filter(Q(id__in=folder_ids)).delete()


async def delete_folders(user_id: int, folder_ids: list):
    """
    删除某一个文件夹（包含子文件夹）
    :param user_id:
    :param folder_ids:
    :return:
    """
    for folder_id in folder_ids:
        await delete_folder(user_id, folder_id)


async def create_collect_folder(user_id: int):
    """
    创建收藏夹文件夹
    :param user_id:
    :return:
    """
    folder, is_new = await mysql.Folder.get_or_create(
        user_id=user_id,
        folder_types=mysql.Folder.Type.collect,
        defaults={
            'name': '我的收藏夹',
            'description': '',
            'parent_folder_id': 0,
            'filter_condition': '',
            'parent_path': '',
            'level': 0,
        },
    )
    if is_new:
        folder.path = path_str_arr('', folder.id)
        await folder.save()
    return folder


async def get_user_root_folder_ids(user_id: int, offset: int = 0, limit: int = 10):
    """
    获取用户一级文件夹，包含回收站：编号为-1
    :param user_id:
    :param offset:
    :param limit:
    :return:
    """
    folder_ids = await mysql.Folder.filter(user_id=user_id, level=0).order_by('folder_types', '-created_time')\
        .offset(offset).limit(limit).values_list('id', flat=True)
    folder_count = await mysql.Folder.filter(user_id=user_id, level=0).count()
    folder_count += 1  # 补充回收站名额
    # 判断是否是最后一页
    page_count = offset + limit
    if page_count >= folder_count and page_count - folder_count < limit:
        folder_ids.append(-1)
    return folder_ids


async def get_user_folder_ids_by_search(user_id: int, keyword: str = '', offset: int = 0, limit: int = 10):
    """
    搜索用户文件夹
    :param user_id:
    :param keyword:
    :param offset:
    :param limit:
    :return:
    """
    folder_ids = await mysql.Folder.filter(name__icontains=keyword, user_id=user_id,
                                           folder_types=mysql.Folder.Type.common).order_by('level', '-created_time')\
        .offset(offset).limit(limit).values_list('id', flat=True)
    return folder_ids


async def get_folder_all_files(folder_id: int, offset: int = 0, limit: int = 10):
    """
    获取文件夹所有文件（包含子文件夹）
    :param folder_id:
    :param offset:
    :param limit:
    :return:
    """
    # 获取子文件夹编号
    conn = Tortoise.get_connection("default")
    items = await conn.execute_query_dict(
        "SELECT e2.id FROM folder e1, folder e2 "
        f"WHERE e1.id={folder_id} AND e2.path like concat(e1.path,'/%')")
    child_folder_ids = [i.get('id') for i in items]
    folder_ids = [folder_id]
    # 此文件夹及子文件夹
    folder_ids.extend(child_folder_ids)
    files = await mysql.FolderFile.filter(Q(folder_id__in=folder_ids)).order_by('-created_time').offset(offset)\
        .limit(limit)
    return files


async def reset_folder_covers(folder_id):
    """
    重置文件夹封面
    :param folder_id:
    :return:
    """
    # 获取子文件夹编号
    conn = Tortoise.get_connection("default")
    items = await conn.execute_query_dict(
        "SELECT e2.id FROM folder e1, folder e2 "
        f"WHERE e1.id={folder_id} AND e2.path like concat(e1.path,'/%')")
    child_folder_ids = [i.get('id') for i in items]
    folder_ids = [folder_id]
    # 此文件夹及子文件夹
    folder_ids.extend(child_folder_ids)
    # 获取前4个媒体的图片信息
    media_ids = await mysql.FolderFile.filter(Q(folder_id__in=folder_ids)).order_by('-created_time').offset(0)\
        .limit(4).values_list('media_id', flat=True)
    media_ids_unique = list(set(media_ids))
    media_exif_dict = {}
    for media_id in media_ids_unique:
        media = await mysql.Media.get_or_none(id=media_id)
        if media:
            if media.object_type == mysql.Media.Type.photo:
                data = await mysql.Photo.get_or_none(id=media.object_id)
                if data:
                    media_exif_dict[media_id] = json.loads(data.file_exif or '{}')
            elif media.object_type == mysql.Media.Type.case:
                photo_media = await mysql.Media.filter(parent_id=media.id).first()
                data = await mysql.Photo.get_or_none(id=photo_media.object_id) if photo_media else None
                if data:
                    media_exif_dict[media_id] = json.loads(data.file_exif or '{}')
            elif media.object_type == mysql.Media.Type.video:
                data = await mysql.Video.get_or_none(id=media.object_id)
                if data:
                    media_exif_dict[media_id] = json.loads(data.file_exif or '{}')
            elif media.object_type == mysql.Media.Type.panorama:
                data = await mysql.Panorama.get_or_none(id=media.object_id)
                if data:
                    media_exif_dict[media_id] = json.loads(data.file_exif or '{}')
    covers = []
    for media_id in media_ids:
        media_exif = media_exif_dict.get(media_id)
        if media_exif:
            covers.append(media_exif)
    # 入库
    folder_covers = await mysql.FolderCovers.get_or_none(id=folder_id)
    if folder_covers:
        await mysql.FolderCovers.filter(id=folder_id).update(covers=json.dumps(covers, ensure_ascii=False))
    else:
        await mysql.FolderCovers.create(id=folder_id, covers=json.dumps(covers, ensure_ascii=False))


async def reset_folder_and_parent_covers(folder_id: int):
    """
    重置文件夹及父级封面
    :param folder_id:
    :return:
    """
    folder = await mysql.Folder.get_or_none(id=folder_id)
    if not folder:
        return
    parent_path = folder.parent_path or ''
    parent_ids = parent_path.strip('/') if parent_path else ''
    folder_parent_ids = [int(i) for i in parent_ids.split('/')] if parent_ids else []
    folder_ids = [folder_id]
    folder_ids.extend(folder_parent_ids)
    for f_id in folder_ids:
        await reset_folder_covers(f_id)


async def get_user_collect_folder(user_id: int):
    """
    获取收藏夹
    :param user_id:
    :return:
    """
    return await mysql.Folder.get_or_none(user_id=user_id, folder_types=mysql.Folder.Type.collect)


async def get_root_folder_by_folder_id(folder_id, folder=None):
    """
    获取某个文件夹的一级文件夹
    :param folder_id:
    :param folder:
    :return:
    """
    folder = folder or await mysql.Folder.get_or_none(id=folder_id)
    if folder:
        parent_path = folder.parent_path or ''
        parent_ids = parent_path.strip('/') if parent_path else ''
        folder_parent_ids = [int(i) for i in parent_ids.split('/')] if parent_ids else []
        if folder_parent_ids:
            folder_parent_id = folder_parent_ids[0]
            root_folder = await mysql.Folder.get_or_none(id=folder_parent_id)
            return root_folder
        else:
            return folder
